#include <time.h>

#include "Game.h"
#include "BlocksCan.h"
#include "Blocks.h"

const unsigned char ESC		= 27;
const unsigned char SPACE	= 32;

unsigned Game::_speed		= 1;
unsigned Game::_minSpeed	= 1;
unsigned Game::_maxSpeed	= 9;

Game::Game() 
: _lines(0)
, _score(0)
, _lose(false)
, _paused(false)
, _nextFigIndex(rand() % VARIOUS_BLOCKS)
, _nextRotIndex(rand() % ROTATIONS)
{
	srand(time(NULL));
	
	_can	= std::tr1::shared_ptr<BlocksCan>(new BlocksCan(this));
	_block	= std::tr1::shared_ptr<Block>(new Block(_can.get()));
	
	_StartNextBlock();
}

#pragma warning(disable : 4100) //to supress unused warning int x, int y needed by GLUT API
void Game::KeyPressed(int specialKey, unsigned char key, int x, int y)
{
	switch (specialKey)
	{
		case GLUT_KEY_UP	:	
		case GLUT_KEY_DOWN	:	
		case GLUT_KEY_LEFT	:	
		case GLUT_KEY_RIGHT	:	_InGameKey(specialKey, 0);	break;
	}

	switch (key)
	{
		case SPACE			:	_InGameKey(0, key);			break;
		case '+'			:	
		case '-'			:	
		case 'p'			:			
		case 'P'			:	
		case 'r'			:   
		case 'R'			:	
		case ESC			:	_ControlKey(0, key);		break;
	}
}

void Game::_ControlKey(int specialKey, unsigned char key)
{
	switch (key)
	{
		case '+'			:	_SpeedUp();					break;
		case '-'			:	_SpeedDown();				break;
		case 'p'			:			
		case 'P'			:	Pause();					break;
		case 'r'			:   
		case 'R'			:	Restart();					break;
		case ESC			:	Stop();						break;
	}
}
#pragma warning(default : 4100)


void Game::_InGameKey(int specialKey, unsigned char key)
{
	if (!_paused && !_lose)
	{
		switch (specialKey)
		{
			case GLUT_KEY_UP	:	_block->Rotate();		break;
			case GLUT_KEY_DOWN	:	_block->MoveDown();		break;
			case GLUT_KEY_LEFT	:	_block->MoveLeft();		break;
			case GLUT_KEY_RIGHT	:	_block->MoveRight();	break;
		}

		switch (key)
		{
			case SPACE			:	_block->FallDown();
									Update();				break;
		}
	}
}

void Game::Draw()
{
	unsigned x0 = 50, y0 = 50, x0new = 0, y0new = 0;

	glClear(GL_COLOR_BUFFER_BIT);
	_DrawCan(x0, y0, x0new, y0new);
	_DrawStatus(x0new, y0);

	glutSwapBuffers();
}

void Game::_DrawStatus(unsigned x0, unsigned y0)
{
	_DrawText(x0, y0 + 20, GLUT_BITMAP_HELVETICA_12, "Lines: %i", _lines);
	_DrawText(x0, y0 + 40, GLUT_BITMAP_HELVETICA_12, "Score: %i", _score);
	_DrawText(x0, y0 + 60, GLUT_BITMAP_HELVETICA_12, "Speed: %i", _speed);

	_DrawText(x0, y0 + 420, GLUT_BITMAP_HELVETICA_18, " Next");
	_DrawNext(x0, y0 + 460);

	if (_lose)
	{
		_DrawText(x0, y0 + 240, GLUT_BITMAP_HELVETICA_18, "    Game over."     );
		_DrawText(x0, y0 + 220, GLUT_BITMAP_HELVETICA_18, "Press R to restart.");
	}
	else if (_paused)
	{
		_DrawText(x0, y0 + 240, GLUT_BITMAP_HELVETICA_18, "   Game paused."     );
		_DrawText(x0, y0 + 220, GLUT_BITMAP_HELVETICA_18, "Press P to continue.");
	}
}

void Game::_DrawText(unsigned x, unsigned y, void* font, char* format, ...)
{
	static char buffer[256];

	va_list args;
    va_start(args, format);
	vsnprintf_s(buffer, 256, 255, format, args);
    va_end(args);

	glRasterPos2i(x, y);
	for (char* str = buffer; *str != '\0'; ++str)
		glutBitmapCharacter(font, *str);
}

void Game::_DrawNext(unsigned x0, unsigned y0)
{
	for (unsigned cell = 0; cell < FIGURE_COMPLEXITY; ++cell)
	{
		unsigned x = x0 + _blocks[_nextFigIndex][_nextRotIndex][2*cell]*_can->CellSize();
		unsigned y = y0 + _blocks[_nextFigIndex][_nextRotIndex][2*cell + 1]*_can->CellSize();

		_DrawCell(x, y, x + _can->CellSize(), y + _can->CellSize(), _nextColor);
	}
}

void Game::_DrawBorder(unsigned x0, unsigned y0, unsigned width, unsigned height)
{
	glColor3ub(255, 255, 255);

	glBegin(GL_LINE_STRIP);
	
	glVertex2f(x0, y0 + height);
	glVertex2f(x0, y0);
	glVertex2f(x0 + width, y0);
	glVertex2f(x0 + width, y0 + height);
	
	glEnd();
}

void Game::_DrawGrid(unsigned x0, unsigned y0, unsigned vLines, unsigned hLines, unsigned step)
{
	glColor3ub(50, 50, 50);

	glLineStipple(3, 0xAAAA);
	glBegin(GL_LINES);

	unsigned width  = x0 + vLines * step;
	unsigned height = y0 + hLines * step;

	for (unsigned x = x0; x <= width; x += step)
	{
		glVertex2f(x, y0);
		glVertex2f(x, height);
	}

	for (unsigned y = y0; y <= height; y += step)
	{
		glVertex2f(x0	, y);
		glVertex2f(width, y);	
	}
	
	glEnd();
}

void Game::_DrawCan(unsigned x0, unsigned y0, unsigned& x0new, unsigned& y0new)
{
	unsigned rectX1 = x0, rectX2 = rectX1 + _can->CellSize();
	unsigned rectY1 = y0, rectY2 = rectY1 + _can->CellSize();

	_DrawGrid(x0, y0, _can->Width(), _can->RedLine(), _can->CellSize());
	_DrawBorder(x0, y0, _can->Width() * _can->CellSize(), _can->Height() * _can->CellSize());


	for (unsigned width = 0; width < _can->Width(); ++width)
	{
		rectY1 = y0;
		rectY2 = rectY1 + _can->CellSize();
		
		for (unsigned height = 0; height < _can->Height(); ++height)
		{
			_DrawCell(rectX1, rectY1, rectX2, rectY2, _can->Cell(width, height) & COLOR_MASK);
			
			rectY1  = rectY2;
			rectY2 += _can->CellSize();
		}

		rectX1  = rectX2;
		rectX2 += _can->CellSize();
	}

	x0new = rectX2;
	y0new = rectY2;
}

void Game::_DrawCell(GLint x1, GLint y1, GLint x2, GLint y2, char color)
{
	if (color)
	{
		unsigned char red   = (Color[color-1] & 0xff0000) >> 16;
		unsigned char green = (Color[color-1] & 0x00ff00) >> 8;
		unsigned char blue  = (Color[color-1] & 0x0000ff);
		
		glColor3ub(red, green, blue);
		glRecti(x1 + 1, y1 + 1, x2 - 1, y2 - 1);
		glColor3ub(255, 255, 255);
	}
}	

void Game::ReportLinesRemoval(unsigned numberOf)
{
	unsigned baseScore = 100;

	_lines += numberOf;
	_score += numberOf*baseScore;

	while (numberOf--) 
		_score += numberOf*(baseScore >>= 1);
}

void Game::_StartNextBlock()
{
	_block->Update(_nextFigIndex, _nextRotIndex); 
	
	_nextFigIndex = rand() % VARIOUS_BLOCKS;
	_nextRotIndex = rand() % ROTATIONS;
	_nextColor    = _nextFigIndex + 1;
	
	_block->PutOnTop();
}

void Game::Update()
{
	if (!_paused && !_block->MoveDown())
	{
		_block->CheckLines();
		if (!_lose)
			_StartNextBlock();
	}
}

void Game::Restart()
{
	_lose   = false;
	_paused = false;
	_lines  = 0;
	_score  = 0;

	_can->Clear();
	_StartNextBlock();
}

void Game::Pause()
{
	_paused = !_paused;
}

void Game::Stop()
{
	exit(0);	
}

void Game::ReportLoseState()
{
    _lose = true;
}

/* static */ unsigned Game::Interval()
{
    return (_maxSpeed - _speed + 1)*100;
}

void Game::_SpeedUp()
{
    if (_speed < _maxSpeed) ++_speed; 
}

void Game::_SpeedDown()
{
    if (_speed > _minSpeed) --_speed; 
}